# -*- coding: utf-8 -*-
# @File     : authentication.py
# @Author   : bingjia
# @Time     : 2020/7/23 15:20
# @Desc     : django用户认证，提供认证基类以及其它认证策略

import base64

from django.contrib.auth import authenticate
from django.middleware.csrf import CsrfViewMiddleware

from . import exceptions
from . import HTTP_HEADER_ENCODING


def get_authorization_header(request):
    """
    获取request的'Authorization'头
    :param request:
    :return:
    """
    auth = request.META.get('HTTP_AUTHORIZATION', b'')
    if isinstance(auth, str):
        auth = auth.encode(HTTP_HEADER_ENCODING)
    return auth


class CSRFCheck(CsrfViewMiddleware):
    def _reject(self, request, reason):
        # Return the failure reason instead of an HttpResponse
        return reason


class BaseAuthentication(object):
    """
    所有authentication类需要继承的基类
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).继承类需要重写
        """
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        pass


class BasicAuthentication(BaseAuthentication):
    """
    针对用户名/密码的方式的http基本身份认证
    """
    www_authenticate_realm = 'api'

    def authenticate(self, request):
        """
        如果用户名/密码通过了http基本身份认证，则返回'User'，否则返回'None'
        :param request:
        :return:
        """
        auth = get_authorization_header(request).split()

        if not auth or auth[0].lower() != b'basic':
            return None

        if len(auth) == 1:
            msg = 'Invalid basic header. No credentials provided.'
            raise exceptions.AuthenticationFailed(msg)
        elif len(auth) > 2:
            msg = 'Invalid basic header. Credentials string should not contain spaces.'
            raise exceptions.AuthenticationFailed(msg)

        try:
            auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
        except (TypeError, UnicodeEncodeError):
            msg = 'Invalid basic header. Credentials not correctly base64 encoded'
            raise exceptions.AuthenticationFailed(msg)

        userid, password = auth_parts[0], auth_parts[2]

        return self.authenticate_credentials(userid, password)

    def authenticate_header(self, request):
        return 'Basic realm="%s"' % self.www_authenticate_realm

    def authenticate_credentials(self, userid, password):
        user = authenticate(username=userid, password=password)
        if user is None or not user.is_active:
            raise exceptions.AuthenticationFailed('Invalid username/password')
        return (user, None)


class SessionAuthentication(BaseAuthentication):
    """
    通过django session认证
    """
    def authenticate(self, request):
        """
        如果请求会话当前有一个已登陆用户，则返回'User'，否则返回'None'
        :param request:
        :return:
        """
        # request被封装了，原生request存储在request._request里面
        request = request._request
        user = getattr(request, 'user', None)

        if not user or not user.is_active:
            return None

        self.enforce_csrf(request)

        return (user, None)

    def enforce_csrf(self, request):
        reason = CSRFCheck().process_view(request, None, (), {})
        if reason:
            # CSRF failed, bail with explicit error message
            raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason)
